/*******************************************************************************
 * Copyright (c) 2004, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.core.search.indexing;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchDocument;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.internal.compiler.ISourceElementRequestor;
import org.eclipse.jdt.internal.compiler.SourceElementParser;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
import org.eclipse.jdt.internal.compiler.util.SimpleSet;
import org.eclipse.jdt.internal.core.ClasspathEntry;
import org.eclipse.jdt.internal.core.JavaModel;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.index.DiskIndex;
import org.eclipse.jdt.internal.core.index.FileIndexLocation;
import org.eclipse.jdt.internal.core.index.Index;
import org.eclipse.jdt.internal.core.index.IndexLocation;
import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
import org.eclipse.jdt.internal.core.search.PatternSearchJob;
import org.eclipse.jdt.internal.core.search.processing.IJob;
import org.eclipse.jdt.internal.core.search.processing.JobManager;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import java.util.zip.CRC32;

public class IndexManager extends JobManager implements IIndexConstants {

    public static final  Integer           SAVED_STATE                          = new Integer(0);
    public static final  Integer           UPDATING_STATE                       = new Integer(1);
    public static final  Integer           UNKNOWN_STATE                        = new Integer(2);
    public static final  Integer           REBUILDING_STATE                     = new Integer(3);
    public static final  Integer           REUSE_STATE                          = new Integer(4);
    // should JDT manage (update, delete as needed) pre-built indexes?
    public static final  String            MANAGE_PRODUCT_INDEXES_PROPERTY      = "jdt.core.manageProductIndexes"; //$NON-NLS-1$
    private static final boolean           IS_MANAGING_PRODUCT_INDEXES_PROPERTY = Boolean.getBoolean(MANAGE_PRODUCT_INDEXES_PROPERTY);
    // Debug
    public static        boolean           DEBUG                                = false;
    // key = containerPath, value = indexLocation path
    // indexLocation path is created by appending an index file name to the getJavaPluginWorkingLocation() path
    public               SimpleLookupTable indexLocations                       = new SimpleLookupTable();
    // key = indexLocation path, value = an index
    private              SimpleLookupTable indexes                              = new SimpleLookupTable();
    /* need to save ? */
    private              boolean           needToSave                           = false;
    private              IPath             javaPluginLocation                   = null;
    /* can only replace a current state if its less than the new one */
    // key = indexLocation path, value = index state integer
    private              SimpleLookupTable indexStates                          = null;
    private File indexNamesMapFile;
    //$NON-NLS-1$
    private File savedIndexNamesFile;
    //$NON-NLS-1$
    private File participantIndexNamesFile;
    //$NON-NLS-1$
    private boolean           javaLikeNamesChanged   = true;
    // search participants who register indexes with the index manager
    private SimpleLookupTable participantsContainers = null;
    private boolean           participantUpdated     = false;
    private String indexLocation;

    public IndexManager(String indexLocation) {
        this.indexLocation = indexLocation;
        indexNamesMapFile = new File(getSavedIndexesDirectory(), "indexNamesMap.txt");
        savedIndexNamesFile = new File(getSavedIndexesDirectory(), "savedIndexNames.txt");
        participantIndexNamesFile = new File(getSavedIndexesDirectory(), "participantsIndexNames.txt");
    }

    public synchronized void aboutToUpdateIndex(IPath containerPath, Integer newIndexState) {
        // newIndexState is either UPDATING_STATE or REBUILDING_STATE
        // must tag the index as inconsistent, in case we exit before the update job is started
        IndexLocation indexLocation = computeIndexLocation(containerPath);
        Object state = getIndexStates().get(indexLocation);
        Integer currentIndexState = state == null ? UNKNOWN_STATE : (Integer)state;
        if (currentIndexState.compareTo(REBUILDING_STATE) >= 0) return; // already rebuilding the index

        int compare = newIndexState.compareTo(currentIndexState);
        if (compare > 0) {
            // so UPDATING_STATE replaces SAVED_STATE and REBUILDING_STATE replaces everything
            updateIndexState(indexLocation, newIndexState);
        } else if (compare < 0 && this.indexes.get(indexLocation) == null) {
            // if already cached index then there is nothing more to do
            rebuildIndex(indexLocation, containerPath);
        }
    }

    /**
     * Trigger addition of a resource to an index
     * Note: the actual operation is performed in background
     */
    public void addBinary(IFile resource, IPath containerPath) {
//        if (JavaCore.getPlugin() == null) return;
        SearchParticipant participant = SearchEngine.getDefaultSearchParticipant();
        SearchDocument document = participant.getDocument(resource.getFullPath().toString());
        IndexLocation indexLocation = computeIndexLocation(containerPath);
        scheduleDocumentIndexing(document, containerPath, indexLocation, participant);
    }

    /**
     * Trigger addition of a resource to an index
     * Note: the actual operation is performed in background
     */
    public void addSource(IFile resource, IPath containerPath, SourceElementParser parser) {
//        if (JavaCore.getPlugin() == null) return;
        SearchParticipant participant = SearchEngine.getDefaultSearchParticipant();
        SearchDocument document = participant.getDocument(resource.getFullPath().toString());
        document.setParser(parser);
        IndexLocation indexLocation = computeIndexLocation(containerPath);
        scheduleDocumentIndexing(document, containerPath, indexLocation, participant);
    }

    /*
     * Removes unused indexes from disk.
     */
    public void cleanUpIndexes() {
        SimpleSet knownPaths = new SimpleSet();
        IJavaSearchScope scope = BasicSearchEngine.createWorkspaceScope();
        PatternSearchJob job = new PatternSearchJob(null, SearchEngine.getDefaultSearchParticipant(), scope, null);
        Index[] selectedIndexes = job.getIndexes(null);
        for (int i = 0, l = selectedIndexes.length; i < l; i++) {
            IndexLocation IndexLocation = selectedIndexes[i].getIndexLocation();
            knownPaths.add(IndexLocation);
        }

        if (this.indexStates != null) {
            Object[] keys = this.indexStates.keyTable;
            IndexLocation[] locations = new IndexLocation[this.indexStates.elementSize];
            int count = 0;
            for (int i = 0, l = keys.length; i < l; i++) {
                IndexLocation key = (IndexLocation)keys[i];
                if (key != null && !knownPaths.includes(key))
                    locations[count++] = key;
            }
            if (count > 0)
                removeIndexesState(locations);
        }
        deleteIndexFiles(knownPaths);
    }

    /**
     * Compute the pre-built index location for a specified URL
     */
    public synchronized IndexLocation computeIndexLocation(IPath containerPath, final URL newIndexURL) {
        IndexLocation indexLocation = (IndexLocation)this.indexLocations.get(containerPath);
        if (indexLocation == null) {
            if (newIndexURL != null) {
                indexLocation = IndexLocation.createIndexLocation(newIndexURL);
                // update caches
                indexLocation = (IndexLocation)getIndexStates().getKey(indexLocation);
                this.indexLocations.put(containerPath, indexLocation);
            }
        } else {
            // an existing index location exists - make sure it has not changed (i.e. the URL has not changed)
            URL existingURL = indexLocation.getUrl();
            if (newIndexURL != null) {
                // if either URL is different then the index location has been updated so rebuild.
                if (!newIndexURL.equals(existingURL)) {
                    // URL has changed so remove the old index and create a new one
                    this.removeIndex(containerPath);
                    // create a new one
                    indexLocation = IndexLocation.createIndexLocation(newIndexURL);
                    // update caches
                    indexLocation = (IndexLocation)getIndexStates().getKey(indexLocation);
                    this.indexLocations.put(containerPath, indexLocation);
                }
            }
        }
        return indexLocation;
    }

    public synchronized IndexLocation computeIndexLocation(IPath containerPath) {
        IndexLocation indexLocation = (IndexLocation)this.indexLocations.get(containerPath);
        if (indexLocation == null) {
            String pathString = containerPath.toOSString();
            CRC32 checksumCalculator = new CRC32();
            checksumCalculator.update(pathString.getBytes());
            String fileName = Long.toString(checksumCalculator.getValue()) + ".index"; //$NON-NLS-1$
            if (JobManager.VERBOSE)
                Util.verbose("-> index name for " + pathString + " is " + fileName); //$NON-NLS-1$ //$NON-NLS-2$
            // to share the indexLocation between the indexLocations and indexStates tables, get the key from the indexStates table
            indexLocation = (IndexLocation)getIndexStates().getKey(new FileIndexLocation(new File(getSavedIndexesDirectory(), fileName)));
            this.indexLocations.put(containerPath, indexLocation);
        }
        return indexLocation;
    }

    public void deleteIndexFiles() {
        if (DEBUG)
            Util.verbose("Deleting index files"); //$NON-NLS-1$
        this.savedIndexNamesFile.delete(); // forget saved indexes & delete each index file
        deleteIndexFiles(null);
        getSavedIndexesDirectory().delete();
    }

    private void deleteIndexFiles(SimpleSet pathsToKeep) {
        File[] indexesFiles = getSavedIndexesDirectory().listFiles();
        if (indexesFiles == null) return;

        for (int i = 0, l = indexesFiles.length; i < l; i++) {
            String fileName = indexesFiles[i].getAbsolutePath();
            if (pathsToKeep != null && pathsToKeep.includes(new FileIndexLocation(indexesFiles[i]))) continue;
            String suffix = ".index"; //$NON-NLS-1$
            if (fileName.regionMatches(true, fileName.length() - suffix.length(), suffix, 0, suffix.length())) {
                if (JobManager.VERBOSE || DEBUG)
                    Util.verbose("Deleting index file " + indexesFiles[i]); //$NON-NLS-1$
                indexesFiles[i].delete();
            }
        }
    }

    /*
     * Creates an empty index at the given location, for the given container path, if none exist.
     */
    public synchronized void ensureIndexExists(IndexLocation indexLocation, IPath containerPath) {
        SimpleLookupTable states = getIndexStates();
        Object state = states.get(indexLocation);
        if (state == null) {
            updateIndexState(indexLocation, REBUILDING_STATE);
            getIndex(containerPath, indexLocation, true, true);
        }
    }

    public SourceElementParser getSourceElementParser(IJavaProject project, ISourceElementRequestor requestor) {
        // disable task tags to speed up parsing
        Map options = project.getOptions(true);
        options.put(JavaCore.COMPILER_TASK_TAGS, ""); //$NON-NLS-1$
        try {
            SourceElementParser parser = new IndexingParser(
                    requestor,
                    new DefaultProblemFactory(Locale.getDefault()),
                    new CompilerOptions(options),
                    true, // index local declarations
                    true, // optimize string literals
                    false); // do not use source javadoc parser to speed up parsing
            parser.reportOnlyOneSyntaxError = true;

            // Always check javadoc while indexing
            parser.javadocParser.checkDocComment = true;
            parser.javadocParser.reportProblems = false;

            return parser;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Returns the index for a given index location
     *
     * @param indexLocation
     *         The path of the index file
     * @return The corresponding index or <code>null</code> if not found
     */
    public synchronized Index getIndex(IndexLocation indexLocation) {
        return (Index)this.indexes.get(indexLocation); // is null if unknown, call if the containerPath must be computed
    }

    /**
     * Returns the index for a given project, according to the following algorithm:
     * - if index is already in memory: answers this one back
     * - if (reuseExistingFile) then read it and return this index and record it in memory
     * - if (createIfMissing) then create a new empty index and record it in memory
     * <p/>
     * Warning: Does not check whether index is consistent (not being used)
     */
    public synchronized Index getIndex(IPath containerPath, boolean reuseExistingFile, boolean createIfMissing) {
        IndexLocation indexLocation = computeIndexLocation(containerPath);
        return getIndex(containerPath, indexLocation, reuseExistingFile, createIfMissing);
    }

    /**
     * Returns the index for a given project, according to the following algorithm:
     * - if index is already in memory: answers this one back
     * - if (reuseExistingFile) then read it and return this index and record it in memory
     * - if (createIfMissing) then create a new empty index and record it in memory
     * <p/>
     * Warning: Does not check whether index is consistent (not being used)
     */
    public synchronized Index getIndex(IPath containerPath, IndexLocation indexLocation, boolean reuseExistingFile,
                                       boolean createIfMissing) {
        // Path is already canonical per construction
        Index index = getIndex(indexLocation);
        if (index == null) {
            Object state = getIndexStates().get(indexLocation);
            Integer currentIndexState = state == null ? UNKNOWN_STATE : (Integer)state;
            if (currentIndexState == UNKNOWN_STATE) {
                // should only be reachable for query jobs
                // IF you put an index in the cache, then AddJarFileToIndex fails because it thinks there is nothing to do
                rebuildIndex(indexLocation, containerPath);
                return null;
            }

            // index isn't cached, consider reusing an existing index file
            String containerPathString = containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString();
            if (reuseExistingFile) {
                if (indexLocation.exists()) { // check before creating index so as to avoid creating a new empty index if file is missing
                    try {
                        index = new Index(indexLocation, containerPathString, true /*reuse index file*/);
                        this.indexes.put(indexLocation, index);
                        return index;
                    } catch (IOException e) {
                        // failed to read the existing file or its no longer compatible
                        if (currentIndexState != REBUILDING_STATE &&
                            currentIndexState != REUSE_STATE) { // rebuild index if existing file is
                            // corrupt, unless the index is already being rebuilt
                            if (JobManager.VERBOSE)
                                Util.verbose("-> cannot reuse existing index: " + indexLocation + " path: " +
                                             containerPathString); //$NON-NLS-1$ //$NON-NLS-2$
                            rebuildIndex(indexLocation, containerPath);
                            return null;
                        }
                    /*index = null;*/ // will fall thru to createIfMissing & create a empty index for the rebuild all job to populate
                    }
                }
                if (currentIndexState == SAVED_STATE) { // rebuild index if existing file is missing
                    rebuildIndex(indexLocation, containerPath);
                    return null;
                }
                if (currentIndexState == REUSE_STATE) {
                    // supposed to be in reuse state but error in the index file, so reindex.
                    if (JobManager.VERBOSE)
                        Util.verbose("-> cannot reuse given index: " + indexLocation + " path: " +
                                     containerPathString); //$NON-NLS-1$ //$NON-NLS-2$
                    this.indexLocations.put(containerPath, null);
                    indexLocation = computeIndexLocation(containerPath);
                    rebuildIndex(indexLocation, containerPath);
                    return null;
                }
            }
            // index wasn't found on disk, consider creating an empty new one
            if (createIfMissing) {
                try {
                    if (JobManager.VERBOSE)
                        Util.verbose(
                                "-> create empty index: " + indexLocation + " path: " + containerPathString); //$NON-NLS-1$ //$NON-NLS-2$
                    index = new Index(indexLocation, containerPathString, false /*do not reuse index file*/);
                    this.indexes.put(indexLocation, index);
                    return index;
                } catch (IOException e) {
                    if (JobManager.VERBOSE)
                        Util.verbose("-> unable to create empty index: " + indexLocation + " path: " +
                                     containerPathString); //$NON-NLS-1$ //$NON-NLS-2$
                    // The file could not be created. Possible reason: the project has been deleted.
                    return null;
                }
            }
        }
        //System.out.println(" index name: " + path.toOSString() + " <----> " + index.getIndexFile().getName());
        return index;
    }

    /**
     * Returns all the existing indexes for a list of index locations.
     * Note that this may trigger some indexes recreation work
     *
     * @param locations
     *         The list of of the index files path
     * @return The corresponding indexes list.
     */
    public Index[] getIndexes(IndexLocation[] locations, IProgressMonitor progressMonitor) {
        // acquire the in-memory indexes on the fly
        int length = locations.length;
        Index[] locatedIndexes = new Index[length];
        int count = 0;
        if (this.javaLikeNamesChanged) {
            this.javaLikeNamesChanged = hasJavaLikeNamesChanged();
        }
        for (int i = 0; i < length; i++) {
            if (progressMonitor != null && progressMonitor.isCanceled()) {
                throw new OperationCanceledException();
            }
            // may trigger some index recreation work
            IndexLocation indexLocation = locations[i];
            Index index = getIndex(indexLocation);
            if (index == null) {
                // only need containerPath if the index must be built
                IPath containerPath = (IPath)this.indexLocations.keyForValue(indexLocation);
                if (containerPath != null) {// sanity check
                    index = getIndex(containerPath, indexLocation, true /*reuse index file*/, false /*do not create if none*/);
                    if (index != null && this.javaLikeNamesChanged && !index.isIndexForJar()) {
                        // When a change in java like names extension has been detected, all
                        // non jar files indexes (i.e. containing sources) need to be rebuilt.
                        // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=286379
                        File indexFile = index.getIndexFile();
                        if (indexFile.exists()) {
                            if (DEBUG)
                                Util.verbose("Change in javaLikeNames - removing index file for " + containerPath); //$NON-NLS-1$
                            indexFile.delete();
                        }
                        this.indexes.put(indexLocation, null);
                        rebuildIndex(indexLocation, containerPath);
                        index = null;
                    }
                } else {
                    if (indexLocation.isParticipantIndex() && indexLocation.exists()) { // the index belongs to non-jdt search participant
                        try {
                            IPath container = getParticipantsContainer(indexLocation);
                            if (container != null) {
                                index = new Index(indexLocation, container.toOSString(), true /*reuse index file*/);
                                this.indexes.put(indexLocation, index);
                            }
                        } catch (IOException e) {
                            // ignore
                        }
                    }
                }
            }
            if (index != null)
                locatedIndexes[count++] = index; // only consider indexes which are ready
        }
        if (this.javaLikeNamesChanged) {
            writeJavaLikeNamesFile();
            this.javaLikeNamesChanged = false;
        }
        if (count < length) {
            System.arraycopy(locatedIndexes, 0, locatedIndexes = new Index[count], 0, count);
        }
        return locatedIndexes;
    }

    public synchronized Index getIndexForUpdate(IPath containerPath, boolean reuseExistingFile, boolean createIfMissing) {
        IndexLocation indexLocation = computeIndexLocation(containerPath);
        if (getIndexStates().get(indexLocation) == REBUILDING_STATE)
            return getIndex(containerPath, indexLocation, reuseExistingFile, createIfMissing);

        return null; // abort the job since the index has been removed from the REBUILDING_STATE
    }

    private SimpleLookupTable getIndexStates() {
        if (this.indexStates != null) return this.indexStates;

        this.indexStates = new SimpleLookupTable();
        File indexesDirectoryPath = getSavedIndexesDirectory();
        char[][] savedNames = readIndexState(getJavaPluginWorkingLocation().toOSString());
        if (savedNames != null) {
            for (int i = 1, l = savedNames.length; i < l; i++) { // first name is saved signature, see readIndexState()
                char[] savedName = savedNames[i];
                if (savedName.length > 0) {
                    IndexLocation indexLocation = new FileIndexLocation(
                            new File(indexesDirectoryPath, String.valueOf(savedName))); // shares indexesDirectoryPath's segments
                    if (JobManager.VERBOSE)
                        Util.verbose("Reading saved index file " + indexLocation); //$NON-NLS-1$
                    this.indexStates.put(indexLocation, SAVED_STATE);
                }
            }
        } else {
            // All the index files are getting deleted and hence there is no need to
            // further check for change in javaLikeNames.
            writeJavaLikeNamesFile();
            this.javaLikeNamesChanged = false;
            deleteIndexFiles();
        }
        readIndexMap();
        return this.indexStates;
    }

    private IPath getParticipantsContainer(IndexLocation indexLocation) {
        if (this.participantsContainers == null) {
            readParticipantsIndexNamesFile();
        }
        return (IPath)this.participantsContainers.get(indexLocation);
    }

    private IPath getJavaPluginWorkingLocation() {
        if (this.javaPluginLocation != null) return this.javaPluginLocation;

        IPath stateLocation = new Path(indexLocation); //JavaCore.getPlugin().getStateLocation();

        return this.javaPluginLocation = stateLocation;
    }

    private File getSavedIndexesDirectory() {
//        return new File(getJavaPluginWorkingLocation().toOSString());
        return new File(indexLocation);
    }

    /*
     * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=286379
     * Returns true if there is a change in javaLikeNames since it
     * has been last stored.
     * The javaLikeNames stored in the file javaLikeNames.txt
     * is compared with the current javaLikeNames and if there is a change, this
     * function returns true. If the file javaLikeNames.txt doesn't exist and there
     * is only one javaLikeName (.java), then this returns false so that no-reindexing
     * happens.
     */
    private boolean hasJavaLikeNamesChanged() {
        char[][] currentNames = org.eclipse.jdt.internal.core.util.Util.getJavaLikeExtensions();
        int current = currentNames.length;
        char[][] prevNames = readJavaLikeNamesFile();
        if (prevNames == null) {
            if (JobManager.VERBOSE && current != 1)
                Util.verbose("No Java like names found and there is atleast one non-default javaLikeName", System.err); //$NON-NLS-1$
            return (current != 1); //Ignore if only java
        }
        int prev = prevNames.length;
        if (current != prev) {
            if (JobManager.VERBOSE)
                Util.verbose("Java like names have changed", System.err); //$NON-NLS-1$
            return true;
        }
        if (current > 1) {
            // Sort the current java like names.
            // Copy the array to avoid modifying the Util static variable
            System.arraycopy(currentNames, 0, currentNames = new char[current][], 0, current);
            Util.sort(currentNames);
        }

        // The JavaLikeNames would have been sorted before getting stored in the file,
        // hence just do a direct compare.
        for (int i = 0; i < current; i++) {
            if (!CharOperation.equals(currentNames[i], prevNames[i])) {
                if (JobManager.VERBOSE)
                    Util.verbose("Java like names have changed", System.err); //$NON-NLS-1$
                return true;
            }
        }
        return false;
    }

    public void indexDocument(SearchDocument searchDocument, SearchParticipant searchParticipant, Index index, IPath indexLocation) {
        try {
            searchDocument.setIndex(index);
            searchParticipant.indexDocument(searchDocument, indexLocation);
        } finally {
            searchDocument.setIndex(null);
        }
    }

    /**
     * Trigger addition of the entire content of a project
     * Note: the actual operation is performed in background
     */
    public void indexAll(IProject project) {
//        if (JavaCore.getPlugin() == null) return;

        // Also request indexing of binaries on the classpath
        // determine the new children
        try {
            JavaModel model = JavaModelManager.getJavaModelManager().getJavaModel();
            JavaProject javaProject = (JavaProject)model.getJavaProject(project);
            // only consider immediate libraries - each project will do the same
            // NOTE: force to resolve CP variables before calling indexer - 19303, so that initializers
            // will be run in the current thread.
            IClasspathEntry[] entries = javaProject.getResolvedClasspath();
            for (int i = 0; i < entries.length; i++) {
                IClasspathEntry entry = entries[i];
                if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY)
                    indexLibrary(entry.getPath(), project, ((ClasspathEntry)entry).getLibraryIndexLocation());
            }
        } catch (JavaModelException e) { // cannot retrieve classpath info
        }

        // check if the same request is not already in the queue
        IndexRequest request = new IndexAllProject(project, this);
        if (!isJobWaiting(request))
            request(request);
    }

    public void indexLibrary(IPath path, IProject requestingProject, URL indexURL) {
        this.indexLibrary(path, requestingProject, indexURL, false);
    }

    /**
     * Trigger addition of a library to an index
     * Note: the actual operation is performed in background
     */
    public void indexLibrary(IPath path, IProject requestingProject, URL indexURL, final boolean updateIndex) {
        // requestingProject is no longer used to cancel jobs but leave it here just in case
        IndexLocation indexFile = null;
        if (indexURL != null) {
            if (IS_MANAGING_PRODUCT_INDEXES_PROPERTY) {
                indexFile = computeIndexLocation(path, indexURL);
            } else {
                indexFile = IndexLocation.createIndexLocation(indexURL);
            }
        }
//        if (JavaCore.getPlugin() == null) return;
        IndexRequest request = null;
        boolean forceIndexUpdate = IS_MANAGING_PRODUCT_INDEXES_PROPERTY && updateIndex;
//        Object target = JavaModel.getTarget(path, true);
//	if (target instanceof IFile) {
//		request = new AddJarFileToIndex((IFile) target, indexFile, this, forceIndexUpdate);
//	} else
//        if (target instanceof File) {
        request = new AddJarFileToIndex(path, indexFile, this, forceIndexUpdate);
//	} else if (target instanceof IContainer) {
//		request = new IndexBinaryFolder((IContainer) target, this);
//        } else {
//            return;
//        }

        // check if the same request is not already in the queue
        if (!isJobWaiting(request))
            request(request);
    }

    synchronized boolean addIndex(IPath containerPath, IndexLocation indexFile) {
        getIndexStates().put(indexFile, REUSE_STATE);
        this.indexLocations.put(containerPath, indexFile);
        Index index = getIndex(containerPath, indexFile, true, false);
        if (index == null) {
            indexFile.close();
            this.indexLocations.put(containerPath, null);
            return false;
        }
        writeIndexMapFile();
        return true;
    }

    /**
     * Index the content of the given source folder.
     */
    public void indexSourceFolder(JavaProject javaProject, IPath sourceFolder, char[][] inclusionPatterns, char[][] exclusionPatterns) {
        IProject project = javaProject.getProject();
        if (this.jobEnd > this.jobStart) {
            // skip it if a job to index the project is already in the queue
            IndexRequest request = new IndexAllProject(project, this);
            if (isJobWaiting(request)) return;
        }

        request(new AddFolderToIndex(sourceFolder, project, inclusionPatterns, exclusionPatterns, this));
    }

    public synchronized void jobWasCancelled(IPath containerPath) {
        IndexLocation indexLocation = computeIndexLocation(containerPath);
        Index index = getIndex(indexLocation);
        if (index != null) {
            index.monitor = null;
            this.indexes.removeKey(indexLocation);
        }
        updateIndexState(indexLocation, UNKNOWN_STATE);
    }

    /**
     * Advance to the next available job, once the current one has been completed.
     * Note: clients awaiting until the job count is zero are still waiting at this point.
     */
    protected synchronized void moveToNextJob() {
        // remember that one job was executed, and we will need to save indexes at some point
        this.needToSave = true;
        super.moveToNextJob();
    }

    /**
     * No more job awaiting.
     */
    protected void notifyIdle(long idlingTime) {
        if (idlingTime > 1000 && this.needToSave) saveIndexes();
    }

    /**
     * Name of the background process
     */
    public String processName() {
        return Messages.process_name;
    }

    private char[][] readJavaLikeNamesFile() {
        try {
            String pathName = getJavaPluginWorkingLocation().toOSString();
            File javaLikeNamesFile = new File(pathName, "javaLikeNames.txt"); //$NON-NLS-1$
            if (!javaLikeNamesFile.exists())
                return null;
            char[] javaLikeNames = org.eclipse.jdt.internal.compiler.util.Util.getFileCharContent(javaLikeNamesFile, null);
            if (javaLikeNames.length > 0) {
                char[][] names = CharOperation.splitOn('\n', javaLikeNames);
                return names;
            }
        } catch (IOException ignored) {
            if (JobManager.VERBOSE)
                Util.verbose("Failed to read javaLikeNames file"); //$NON-NLS-1$
        }
        return null;
    }

    private void rebuildIndex(IndexLocation indexLocation, IPath containerPath) {
        Object target = JavaModelManager.getTarget(containerPath, true);
        if (target == null) return;

        if (JobManager.VERBOSE)
            Util.verbose("-> request to rebuild index: " + indexLocation + " path: " + containerPath); //$NON-NLS-1$ //$NON-NLS-2$

        updateIndexState(indexLocation, REBUILDING_STATE);
        IndexRequest request = null;
        if (target instanceof IProject) {
            IProject p = (IProject)target;
            if (JavaProject.hasJavaNature(p))
                request = new IndexAllProject(p, this);
        } else if (target instanceof IFolder) {
            request = new IndexBinaryFolder((IFolder)target, this);
        } else if (target instanceof IFile) {
            request = new AddJarFileToIndex((IFile)target, null, this);
        } else if (target instanceof File) {
            request = new AddJarFileToIndex(containerPath, null, this);
        }
        if (request != null)
            request(request);
    }

    /**
     * Recreates the index for a given path, keeping the same read-write monitor.
     * Returns the new empty index or null if it didn't exist before.
     * Warning: Does not check whether index is consistent (not being used)
     */
    public synchronized Index recreateIndex(IPath containerPath) {
        // only called to over write an existing cached index...
        String containerPathString = containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString();
        try {
            // Path is already canonical
            IndexLocation indexLocation = computeIndexLocation(containerPath);
            Index index = getIndex(indexLocation);
            ReadWriteMonitor monitor = index == null ? null : index.monitor;

            if (JobManager.VERBOSE)
                Util.verbose("-> recreating index: " + indexLocation + " for path: " + containerPathString); //$NON-NLS-1$ //$NON-NLS-2$
            index = new Index(indexLocation, containerPathString, false /*do not reuse index file*/);
            this.indexes.put(indexLocation, index);
            index.monitor = monitor;
            return index;
        } catch (IOException e) {
            // The file could not be created. Possible reason: the project has been deleted.
            if (JobManager.VERBOSE) {
                Util.verbose("-> failed to recreate index for path: " + containerPathString); //$NON-NLS-1$
                e.printStackTrace();
            }
            return null;
        }
    }

    /**
     * Trigger removal of a resource to an index
     * Note: the actual operation is performed in background
     */
    public void remove(String containerRelativePath, IPath indexedContainer) {
        request(new RemoveFromIndex(containerRelativePath, indexedContainer, this));
    }

    /**
     * Removes the index for a given path.
     * This is a no-op if the index did not exist.
     */
    public synchronized void removeIndex(IPath containerPath) {
        if (JobManager.VERBOSE || DEBUG)
            Util.verbose("removing index " + containerPath); //$NON-NLS-1$
        IndexLocation indexLocation = computeIndexLocation(containerPath);
        Index index = getIndex(indexLocation);
        File indexFile = null;
        if (index != null) {
            index.monitor = null;
            indexFile = index.getIndexFile();
        }
        if (indexFile == null)
            indexFile = indexLocation.getIndexFile(); // index is not cached yet, but still want to delete the file
        if (this.indexStates.get(indexLocation) == REUSE_STATE) {
            indexLocation.close();
            this.indexLocations.put(containerPath, null);
        } else if (indexFile != null && indexFile.exists()) {
            if (DEBUG)
                Util.verbose("removing index file " + indexFile); //$NON-NLS-1$
            indexFile.delete();
        }
        this.indexes.removeKey(indexLocation);
        if (IS_MANAGING_PRODUCT_INDEXES_PROPERTY) {
            this.indexLocations.removeKey(containerPath);
        }
        updateIndexState(indexLocation, null);
    }

    /**
     * Removes all indexes whose paths start with (or are equal to) the given path.
     */
    public synchronized void removeIndexPath(IPath path) {
        if (JobManager.VERBOSE || DEBUG)
            Util.verbose("removing index path " + path); //$NON-NLS-1$
        Object[] keyTable = this.indexes.keyTable;
        Object[] valueTable = this.indexes.valueTable;
        IndexLocation[] locations = null;
        int max = this.indexes.elementSize;
        int count = 0;
        for (int i = 0, l = keyTable.length; i < l; i++) {
            IndexLocation indexLocation = (IndexLocation)keyTable[i];
            if (indexLocation == null)
                continue;
            if (indexLocation.startsWith(path)) {
                Index index = (Index)valueTable[i];
                index.monitor = null;
                if (locations == null)
                    locations = new IndexLocation[max];
                locations[count++] = indexLocation;
                if (this.indexStates.get(indexLocation) == REUSE_STATE) {
                    indexLocation.close();
                } else {
                    if (DEBUG)
                        Util.verbose("removing index file " + indexLocation); //$NON-NLS-1$
                    indexLocation.delete();
                }
            } else {
                max--;
            }
        }
        if (locations != null) {
            for (int i = 0; i < count; i++)
                this.indexes.removeKey(locations[i]);
            removeIndexesState(locations);
            if (this.participantsContainers != null) {
                boolean update = false;
                for (int i = 0; i < count; i++) {
                    if (this.participantsContainers.get(locations[i]) != null) {
                        update = true;
                        this.participantsContainers.removeKey(locations[i]);
                    }
                }
                if (update) writeParticipantsIndexNamesFile();
            }
        }
    }

    /**
     * Removes all indexes whose paths start with (or are equal to) the given path.
     */
    public synchronized void removeIndexFamily(IPath path) {
        // only finds cached index files... shutdown removes all non-cached index files
        ArrayList toRemove = null;
        Object[] containerPaths = this.indexLocations.keyTable;
        for (int i = 0, length = containerPaths.length; i < length; i++) {
            IPath containerPath = (IPath)containerPaths[i];
            if (containerPath == null) continue;
            if (path.isPrefixOf(containerPath)) {
                if (toRemove == null)
                    toRemove = new ArrayList();
                toRemove.add(containerPath);
            }
        }
        if (toRemove != null)
            for (int i = 0, length = toRemove.size(); i < length; i++)
                removeIndex((IPath)toRemove.get(i));
    }

    /**
     * Remove the content of the given source folder from the index.
     */
    public void removeSourceFolderFromIndex(JavaProject javaProject, IPath sourceFolder, char[][] inclusionPatterns,
                                            char[][] exclusionPatterns) {
        IProject project = javaProject.getProject();
        if (this.jobEnd > this.jobStart) {
            // skip it if a job to index the project is already in the queue
            IndexRequest request = new IndexAllProject(project, this);
            if (isJobWaiting(request)) return;
        }

        request(new RemoveFolderFromIndex(sourceFolder, inclusionPatterns, exclusionPatterns, project, this));
    }

    /**
     * Flush current state
     */
    public synchronized void reset() {
        super.reset();
        if (this.indexes != null) {
            this.indexes = new SimpleLookupTable();
            this.indexStates = null;
        }
        this.indexLocations = new SimpleLookupTable();
        this.javaPluginLocation = null;
    }

    /**
     * Resets the index for a given path.
     * Returns true if the index was reset, false otherwise.
     */
    public synchronized boolean resetIndex(IPath containerPath) {
        // only called to over write an existing cached index...
        String containerPathString = containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString();
        try {
            // Path is already canonical
            IndexLocation indexLocation = computeIndexLocation(containerPath);
            Index index = getIndex(indexLocation);
            if (JobManager.VERBOSE) {
                Util.verbose("-> reseting index: " + indexLocation + " for path: " + containerPathString); //$NON-NLS-1$ //$NON-NLS-2$
            }
            if (index == null) {
                // the index does not exist, try to recreate it
                return recreateIndex(containerPath) != null;
            }
            index.reset();
            return true;
        } catch (IOException e) {
            // The file could not be created. Possible reason: the project has been deleted.
            if (JobManager.VERBOSE) {
                Util.verbose("-> failed to reset index for path: " + containerPathString); //$NON-NLS-1$
                e.printStackTrace();
            }
            return false;
        }
    }

    public void saveIndex(Index index) throws IOException {
        // must have permission to write from the write monitor
        if (index.hasChanged()) {
            if (JobManager.VERBOSE)
                Util.verbose("-> saving index " + index.getIndexLocation()); //$NON-NLS-1$
            index.save();
        }
        synchronized (this) {
            IPath containerPath = new Path(index.containerPath);
            if (this.jobEnd > this.jobStart) {
                for (int i = this.jobEnd; i > this.jobStart; i--) { // skip the current job
                    IJob job = this.awaitingJobs[i];
                    if (job instanceof IndexRequest)
                        if (((IndexRequest)job).containerPath.equals(containerPath)) return;
                }
            }
            IndexLocation indexLocation = computeIndexLocation(containerPath);
            updateIndexState(indexLocation, SAVED_STATE);
        }
    }

    /**
     * Commit all index memory changes to disk
     */
    public void saveIndexes() {
        // only save cached indexes... the rest were not modified
        ArrayList toSave = new ArrayList();
        synchronized (this) {
            Object[] valueTable = this.indexes.valueTable;
            for (int i = 0, l = valueTable.length; i < l; i++) {
                Index index = (Index)valueTable[i];
                if (index != null)
                    toSave.add(index);
            }
        }

        boolean allSaved = true;
        for (int i = 0, length = toSave.size(); i < length; i++) {
            Index index = (Index)toSave.get(i);
            ReadWriteMonitor monitor = index.monitor;
            if (monitor == null) continue; // index got deleted since acquired
            try {
                // take read lock before checking if index has changed
                // don't take write lock yet since it can cause a deadlock (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=50571)
                monitor.enterRead();
                if (index.hasChanged()) {
                    if (monitor.exitReadEnterWrite()) {
                        try {
                            saveIndex(index);
                        } catch (IOException e) {
                            if (JobManager.VERBOSE) {
                                Util.verbose("-> got the following exception while saving:", System.err); //$NON-NLS-1$
                                e.printStackTrace();
                            }
                            allSaved = false;
                        } finally {
                            monitor.exitWriteEnterRead();
                        }
                    } else {
                        allSaved = false;
                    }
                }
            } finally {
                monitor.exitRead();
            }
        }
        if (this.participantsContainers != null && this.participantUpdated) {
            writeParticipantsIndexNamesFile();
            this.participantUpdated = false;
        }
        this.needToSave = !allSaved;
    }

    public void scheduleDocumentIndexing(final SearchDocument searchDocument, IPath container, final IndexLocation indexLocation,
                                         final SearchParticipant searchParticipant) {
        request(new IndexRequest(container, this) {
            public boolean execute(IProgressMonitor progressMonitor) {
                if (this.isCancelled || progressMonitor != null && progressMonitor.isCanceled()) return true;

			/* ensure no concurrent write access to index */
                Index index = getIndex(this.containerPath, indexLocation, true, /*reuse index file*/ true /*create if none*/);
                if (index == null) return true;
                ReadWriteMonitor monitor = index.monitor;
                if (monitor == null) return true; // index got deleted since acquired

                try {
                    monitor.enterWrite(); // ask permission to write
                    indexDocument(searchDocument, searchParticipant, index, new Path(indexLocation.getCanonicalFilePath()));
                } finally {
                    monitor.exitWrite(); // free write lock
                }
                return true;
            }

            public String toString() {
                return "indexing " + searchDocument.getPath(); //$NON-NLS-1$
            }
        });
    }

    public String toString() {
        StringBuffer buffer = new StringBuffer(10);
        buffer.append(super.toString());
        buffer.append("In-memory indexes:\n"); //$NON-NLS-1$
        int count = 0;
        Object[] valueTable = this.indexes.valueTable;
        for (int i = 0, l = valueTable.length; i < l; i++) {
            Index index = (Index)valueTable[i];
            if (index != null)
                buffer.append(++count).append(" - ").append(index.toString()).append('\n'); //$NON-NLS-1$
        }
        return buffer.toString();
    }

    private void readIndexMap() {
        try {
            char[] indexMaps = org.eclipse.jdt.internal.compiler.util.Util.getFileCharContent(this.indexNamesMapFile, null);
            char[][] names = CharOperation.splitOn('\n', indexMaps);
            if (names.length >= 3) {
                // First line is DiskIndex signature (see writeIndexMapFile())
                String savedSignature = DiskIndex.SIGNATURE;
                if (savedSignature.equals(new String(names[0]))) {
                    for (int i = 1, l = names.length - 1; i < l; i += 2) {
                        IndexLocation indexPath = IndexLocation.createIndexLocation(new URL(new String(names[i])));
                        if (indexPath == null) continue;
                        this.indexLocations.put(new Path(new String(names[i + 1])), indexPath);
                        this.indexStates.put(indexPath, REUSE_STATE);
                    }
                }
            }
        } catch (IOException ignored) {
            if (JobManager.VERBOSE)
                Util.verbose("Failed to read saved index file names"); //$NON-NLS-1$
        }
        return;
    }

    private char[][] readIndexState(String dirOSString) {
        try {
            char[] savedIndexNames = org.eclipse.jdt.internal.compiler.util.Util.getFileCharContent(this.savedIndexNamesFile, null);
            if (savedIndexNames.length > 0) {
                char[][] names = CharOperation.splitOn('\n', savedIndexNames);
                if (names.length > 1) {
                    // First line is DiskIndex signature + saved plugin working location (see writeSavedIndexNamesFile())
                    String savedSignature = DiskIndex.SIGNATURE + "+" + dirOSString; //$NON-NLS-1$
                    if (savedSignature.equals(new String(names[0])))
                        return names;
                }
            }
        } catch (IOException ignored) {
            if (JobManager.VERBOSE)
                Util.verbose("Failed to read saved index file names"); //$NON-NLS-1$
        }
        return null;
    }

    private void readParticipantsIndexNamesFile() {
        SimpleLookupTable containers = new SimpleLookupTable(3);
        try {
            char[] participantIndexNames =
                    org.eclipse.jdt.internal.compiler.util.Util.getFileCharContent(this.participantIndexNamesFile, null);
            if (participantIndexNames.length > 0) {
                char[][] names = CharOperation.splitOn('\n', participantIndexNames);
                if (names.length >= 3) {
                    // First line is DiskIndex signature  (see writeParticipantsIndexNamesFile())
                    if (DiskIndex.SIGNATURE.equals(new String(names[0]))) {
                        for (int i = 1, l = names.length - 1; i < l; i += 2) {
                            IndexLocation indexLocation = new FileIndexLocation(new File(new String(names[i])), true);
                            containers.put(indexLocation, new Path(new String(names[i + 1])));
                        }
                    }
                }
            }
        } catch (IOException ignored) {
            if (JobManager.VERBOSE)
                Util.verbose("Failed to read participant index file names"); //$NON-NLS-1$
        }
        this.participantsContainers = containers;
        return;
    }

    private synchronized void removeIndexesState(IndexLocation[] locations) {
        getIndexStates(); // ensure the states are initialized
        int length = locations.length;
        boolean changed = false;
        for (int i = 0; i < length; i++) {
            if (locations[i] == null) continue;
            if ((this.indexStates.removeKey(locations[i]) != null)) {
                changed = true;
                if (JobManager.VERBOSE) {
                    Util.verbose("-> index state updated to: ? for: " + locations[i]); //$NON-NLS-1$
                }
            }
        }
        if (!changed) return;

        writeSavedIndexNamesFile();
        writeIndexMapFile();
    }

    private synchronized void updateIndexState(IndexLocation indexLocation, Integer indexState) {
        if (indexLocation == null)
            throw new IllegalArgumentException();

        getIndexStates(); // ensure the states are initialized
        if (indexState != null) {
            if (indexState.equals(this.indexStates.get(indexLocation))) return; // not changed
            this.indexStates.put(indexLocation, indexState);
        } else {
            if (!this.indexStates.containsKey(indexLocation)) return; // did not exist anyway
            this.indexStates.removeKey(indexLocation);
        }

        writeSavedIndexNamesFile();

        if (JobManager.VERBOSE) {
            if (indexState == null) {
                Util.verbose("-> index state removed for: " + indexLocation); //$NON-NLS-1$
            } else {
                String state = "?"; //$NON-NLS-1$
                if (indexState == SAVED_STATE) state = "SAVED"; //$NON-NLS-1$
                else if (indexState == UPDATING_STATE) state = "UPDATING"; //$NON-NLS-1$
                else if (indexState == UNKNOWN_STATE) state = "UNKNOWN"; //$NON-NLS-1$
                else if (indexState == REBUILDING_STATE) state = "REBUILDING"; //$NON-NLS-1$
                else if (indexState == REUSE_STATE) state = "REUSE"; //$NON-NLS-1$
                Util.verbose("-> index state updated to: " + state + " for: " + indexLocation); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }

    }

    public void updateParticipant(IPath indexPath, IPath containerPath) {
        if (this.participantsContainers == null) {
            readParticipantsIndexNamesFile();
        }
        IndexLocation indexLocation = new FileIndexLocation(indexPath.toFile(), true);
        if (this.participantsContainers.get(indexLocation) == null) {
            this.participantsContainers.put(indexLocation, containerPath);
            this.participantUpdated = true;
        }
    }

    private void writeJavaLikeNamesFile() {
        BufferedWriter writer = null;
        String pathName = getJavaPluginWorkingLocation().toOSString();
        try {
            char[][] currentNames = org.eclipse.jdt.internal.core.util.Util.getJavaLikeExtensions();
            int length = currentNames.length;
            if (length > 1) {
                // Sort the current java like names.
                // Copy the array to avoid modifying the Util static variable
                System.arraycopy(currentNames, 0, currentNames = new char[length][], 0, length);
                Util.sort(currentNames);
            }
            File javaLikeNamesFile = new File(pathName, "javaLikeNames.txt"); //$NON-NLS-1$
            writer = new BufferedWriter(new FileWriter(javaLikeNamesFile));
            for (int i = 0; i < length - 1; i++) {
                writer.write(currentNames[i]);
                writer.write('\n');
            }
            if (length > 0)
                writer.write(currentNames[length - 1]);

        } catch (IOException ignored) {
            if (JobManager.VERBOSE)
                Util.verbose("Failed to write javaLikeNames file", System.err); //$NON-NLS-1$
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }

    private void writeIndexMapFile() {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(this.indexNamesMapFile));
            writer.write(DiskIndex.SIGNATURE);
            writer.write('\n');
            Object[] keys = this.indexStates.keyTable;
            Object[] states = this.indexStates.valueTable;
            for (int i = 0, l = states.length; i < l; i++) {
                IndexLocation location = (IndexLocation)keys[i];
                if (location != null && states[i] == REUSE_STATE) {
                    IPath container = (IPath)this.indexLocations.keyForValue(location);
                    if (container != null) {
                        writer.write(location.toString());
                        writer.write('\n');
                        writer.write(container.toOSString());
                        writer.write('\n');
                    }
                }
            }
        } catch (IOException ignored) {
            if (JobManager.VERBOSE)
                Util.verbose("Failed to write saved index file names", System.err); //$NON-NLS-1$
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }

    private void writeParticipantsIndexNamesFile() {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(this.participantIndexNamesFile));
            writer.write(DiskIndex.SIGNATURE);
            writer.write('\n');
            Object[] indexFiles = this.participantsContainers.keyTable;
            Object[] containers = this.participantsContainers.valueTable;
            for (int i = 0, l = indexFiles.length; i < l; i++) {
                IndexLocation indexFile = (IndexLocation)indexFiles[i];
                if (indexFile != null) {
                    writer.write(indexFile.getIndexFile().getPath());
                    writer.write('\n');
                    writer.write(((IPath)containers[i]).toOSString());
                    writer.write('\n');
                }
            }
        } catch (IOException ignored) {
            if (JobManager.VERBOSE)
                Util.verbose("Failed to write participant index file names", System.err); //$NON-NLS-1$
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }

    private void writeSavedIndexNamesFile() {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(this.savedIndexNamesFile));
            writer.write(DiskIndex.SIGNATURE);
            writer.write('+');
            writer.write(getJavaPluginWorkingLocation().toOSString());
            writer.write('\n');
            Object[] keys = this.indexStates.keyTable;
            Object[] states = this.indexStates.valueTable;
            for (int i = 0, l = states.length; i < l; i++) {
                IndexLocation key = (IndexLocation)keys[i];
                if (key != null && states[i] == SAVED_STATE) {
                    writer.write(key.fileName());
                    writer.write('\n');
                }
            }
        } catch (IOException ignored) {
            if (JobManager.VERBOSE)
                Util.verbose("Failed to write saved index file names", System.err); //$NON-NLS-1$
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }
}
